package view.graph;

import datacontainer.ImmuTable;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;



/**
 * This class establishes a general algorithm by which a bar graph
 * is created when passed an ImmuTable. A List of Points of the top left
 * corner of the bars is generated. Next, the graph layout is established
 * (axes, etc.), the bars visualized, and the axis increments and labels
 * drawn.
 * 
 * @author Oren Bukspan, Kannan Raju, Rich Freedman (creator of the label_line
 *         function)
 */
public class BarGraph extends AbstractVisualization {

    private static final int X_VAR = 0;
    private static final int Y_VAR = 1;
    private static final int NUM_Y_INCREMENTS = 15;
    private static final double Y_AXIS_INCREMENT_DISPLACEMENT = .05;
    private static final double X_AXIS_NAME_DISPLACEMENT = .02;
    private static final double Y_AXIS_LABEL_DISPLACEMENT = .05;
    private static final double X_AXIS_LABEL_DISPLACEMENT = .145;
    private static final double TITLE_PROPORTION = 0.05;
    private static final double Y_AXIS_PROPORTION = 0.8;
    private static final double X_AXIS_PROPORTION = 0.85;
    private static final double RIGHT_DISPLACEMENT = .1;
    private static final double BAR_SEPARATION_PROPORTION = .01;
    private static final int THREE_QUARTERS_ROTATION = 270;
    private static final int ONE_QUARTER_ROTATION = 90;
    private static final int RADIAN_CONVERSION_HELPER = 180;
    private static final int TITLE_SCREEN_WIDTH_DIVISOR = 3;
    private static final double UP_VERTICAL_ORIENT = THREE_QUARTERS_ROTATION * Math.PI /
                                                     RADIAN_CONVERSION_HELPER;
    private static final double DOWN_VERTICAL_ORIENT = ONE_QUARTER_ROTATION * Math.PI /
                                                       RADIAN_CONVERSION_HELPER;
    private static final float AXIS_LABEL_FONT_SIZE = 30.0f;
    private static final float DATA_NAME_FONT_SIZE = 12.0f;
    private static final float VISUALIZATION_TITLE_FONT_SIZE = 19.0f;
    private ArrayList<Point2D.Double> myPoints;
    private ArrayList<Integer> myBarXCoors;
    private List<String> myBarNames;
    private List<String> myHeaders;
    private double myMaxValue;
    private int myNumBars;

    @Override
    public void inputData (ImmuTable data) {
        myBarNames = data.getColumn(X_VAR);
        myHeaders = data.getHeaders();

        ArrayList<Double> yValues = new ArrayList<Double>();
        for (int i = 0; i < data.getColumn(Y_VAR).size(); i++) {
            yValues.add(Double.parseDouble(data.getColumn(Y_VAR).get(i)));
        }

        determineBarLocations(data, yValues);
    }

    private void determineBarLocations (ImmuTable res, ArrayList<Double> yValues) {
        myPoints = new ArrayList<Point2D.Double>();
        myMaxValue = getMaxInColumn(res.getColumn(Y_VAR));
        myNumBars = res.getNumRows();
        for (int i = 0; i < yValues.size(); i++) {
            double x =
                    (1 - X_AXIS_PROPORTION) +
                            (i * (X_AXIS_PROPORTION - RIGHT_DISPLACEMENT) / myNumBars);
            double y =
                    Y_AXIS_PROPORTION * (myMaxValue - yValues.get(i)) / myMaxValue +
                            TITLE_PROPORTION;
            myPoints.add(new Point2D.Double(x, y));
        }
    }

    @Override
    public void paintComponent (Graphics2D graphics, Dimension dim) {
        int windowWidth = dim.width;
        int windowHeight = dim.height;

        setUpVisualisationBackground(graphics, windowWidth, windowHeight);
        drawBars(graphics, windowWidth, windowHeight);
        drawIncrementsAndBarNames(graphics, windowWidth, windowHeight);

        drawAxisLabels(graphics, windowWidth, windowHeight);

    }

    private void drawAxisLabels (Graphics2D graphics, int windowWidth, int windowHeight) {
        labelLine(graphics, Y_AXIS_LABEL_DISPLACEMENT * windowWidth, windowHeight / 2,
                  UP_VERTICAL_ORIENT, myHeaders.get(Y_VAR));
        int xVal;
        int yVal;
        yVal =
                (int) ((TITLE_PROPORTION + Y_AXIS_PROPORTION +
                        X_AXIS_LABEL_DISPLACEMENT) * windowHeight);
        xVal = windowWidth / 2;
        Font theFont = graphics.getFont();
        Font theDerivedFont = theFont.deriveFont(AXIS_LABEL_FONT_SIZE);

        graphics.setFont(theDerivedFont);
        graphics.drawString(myHeaders.get(X_VAR), xVal, yVal);
        graphics.setFont(theFont);
    }

    @Override
    public void setUpVisualisationBackground (Graphics2D graphics,
                                              int windowWidth, int windowHeight) {
        graphics.setColor(Color.BLACK);
        graphics.fillRect(0, 0, windowWidth, windowHeight);
        graphics.setColor(Color.WHITE);
        graphics.drawLine((int) ((1 - X_AXIS_PROPORTION) * windowWidth),
                          (int) (TITLE_PROPORTION * windowHeight),
                          (int) ((1 - X_AXIS_PROPORTION) * windowWidth),
                          (int) ((TITLE_PROPORTION + Y_AXIS_PROPORTION) * windowHeight));

        graphics.drawLine((int) ((1 - X_AXIS_PROPORTION) * windowWidth),
                          (int) ((TITLE_PROPORTION + Y_AXIS_PROPORTION) * windowHeight),
                          (int) (windowWidth * (1 - RIGHT_DISPLACEMENT)),
                          (int) ((TITLE_PROPORTION + Y_AXIS_PROPORTION) * windowHeight));
        int xVal;
        int yVal;
        yVal = (int) (TITLE_PROPORTION * windowHeight);
        xVal = windowWidth / TITLE_SCREEN_WIDTH_DIVISOR;
        Font originalFont = graphics.getFont();
        Font titleFont = graphics.getFont();
        titleFont = titleFont.deriveFont(VISUALIZATION_TITLE_FONT_SIZE);
        graphics.setFont(titleFont);
        graphics.drawString(myHeaders.get(Y_VAR) + " vs " + myHeaders.get(X_VAR), xVal, yVal);
        graphics.setFont(originalFont);
    }

    private void drawIncrementsAndBarNames (Graphics2D graphics,
                                            int windowWidth, int windowHeight) {
        int xVal;
        int yVal;
        xVal = (int) ((1 - X_AXIS_PROPORTION - Y_AXIS_INCREMENT_DISPLACEMENT) * windowWidth);
        for (int i = 0; i < NUM_Y_INCREMENTS; i++) {
            yVal = (int) ((TITLE_PROPORTION + i / ((double) (NUM_Y_INCREMENTS - 1)) *
                                               Y_AXIS_PROPORTION) * windowHeight);
            double curVal = myMaxValue - i / ((double) (NUM_Y_INCREMENTS - 1)) * myMaxValue;
            graphics.drawString("" + (int) curVal, xVal, yVal);
        }
        yVal = (int) ((TITLE_PROPORTION + Y_AXIS_PROPORTION +
                        X_AXIS_NAME_DISPLACEMENT) * windowHeight);
        for (int i = 0; i < myNumBars; i++) {
            labelLine(graphics, myBarXCoors.get(i), yVal, DOWN_VERTICAL_ORIENT, myBarNames.get(i));
        }
    }

    private void drawBars (Graphics2D graphics, int windowWidth, int windowHeight) {
        myBarXCoors = new ArrayList<Integer>();
        int barWidth =
                (int) ((windowWidth * (X_AXIS_PROPORTION - RIGHT_DISPLACEMENT) /
                        myNumBars) - BAR_SEPARATION_PROPORTION *
                        (windowWidth * (X_AXIS_PROPORTION - RIGHT_DISPLACEMENT)));
        for (Point2D.Double bar : myPoints) {
            int xCoor = (int) (windowWidth * bar.getX());
            int yCoor = (int) (windowHeight * bar.getY());
            int barHeight = (int) ((Y_AXIS_PROPORTION + TITLE_PROPORTION) * windowHeight - yCoor);
            myBarXCoors.add((xCoor + xCoor + barWidth) / 2);
            graphics.setColor(Color.RED);
            graphics.fillRect(xCoor, yCoor, barWidth, barHeight);
            graphics.setColor(Color.WHITE);
            graphics.drawRect(xCoor, yCoor, barWidth, barHeight);
        }
    }

    private void labelLine (Graphics2D g2D, double x, double y, double theta, String label) {
        AffineTransform fontAT = new AffineTransform();
        Font theFont = g2D.getFont();
        fontAT.rotate(theta);
        Font theDerivedFont = theFont.deriveFont(fontAT);
        if (theta == UP_VERTICAL_ORIENT) {
            theDerivedFont = theDerivedFont.deriveFont(AXIS_LABEL_FONT_SIZE);
        }
        else {
            theDerivedFont = theDerivedFont.deriveFont(DATA_NAME_FONT_SIZE);
        }
        g2D.setFont(theDerivedFont);
        g2D.drawString(label, (int) x, (int) y);
        g2D.setFont(theFont);
    }

}
